// --------------------------------------------------------------------------------------------------------------------
// <copyright file="BaseForwarder.cs" company="Microsoft Corporation">
// The MIT License (MIT)
// 
// Copyright (c) 2014, Microsoft Corporation
// 
// Permission is hereby granted, free of charge, to any person obtaining a copy
//  of this software and associated documentation files (the "Software"), to deal
//  in the Software without restriction, including without limitation the rights
//  to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
//  copies of the Software, and to permit persons to whom the Software is
//  furnished to do so, subject to the following conditions:
// 
// The above copyright notice and this permission notice shall be included in
//  all copies or substantial portions of the Software.
// 
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
//  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
//  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
//  AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
//  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
//  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
//  THE SOFTWARE.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------
namespace Microsoft.Robotics.Runtime
{
    using System;
    using System.Threading;

    /// <summary>
    /// Implements the common functionality to timestamp the message. 
    /// </summary>
    /// <typeparam name="TMessage">The type of message this forwarder supports</typeparam>
    internal abstract class BaseForwarder<TMessage> : IForwarder<TMessage>
        where TMessage : AgentMessage
    {
        /// <summary>
        /// The next sequence ID for messages published by this agent.
        /// </summary>
        private long nextSequenceId;

        /// <summary>
        /// The time when the pipeline was created. This is used as the time origin for all timestamps.
        /// </summary>
        private long pipelineCreationTime;

        /// <summary>
        /// Name of the pipeline element that created the forwarder
        /// </summary>
        private string creatingPipelineElementName;

        /// <summary>
        /// Initializes a new instance of the BaseForwarder class.
        /// </summary>
        /// <param name="pipelineCreationTime">The time when the pipeline was created. This is used as the time origin for all timestamps.</param>
        /// <param name="creatingPipelineElementName"> Name of the pipeline element that created the forwarder.</param>
        protected BaseForwarder(long pipelineCreationTime, string creatingPipelineElementName)
        {
            this.pipelineCreationTime = pipelineCreationTime;
            this.creatingPipelineElementName = creatingPipelineElementName;

            // the sequence ID is initialized with an absolute timestamp so that consumers of these messages that pay attention to seqIDs (like the UDP listener) 
            // are not affected by a restart of the pipeline (or of the process hosting the pipeline)
            this.nextSequenceId = pipelineCreationTime;
        }

        /// <summary>
        /// Publish the message to all subscribing ports.
        /// </summary>
        /// <param name="message">The message to publish.</param>
        /// <param name="blockUntilDelivered">
        /// If true, the call blocks if the message cannot be consumed, e.g. because an older message is waiting to be delivered. 
        /// The default behavior is "fire and forget", e.g. to replace the older message.
        /// </param>
        public abstract void Post(TMessage message, bool blockUntilDelivered = false);

        /// <summary>
        /// Configures the message with the right timestamp, sequenceId, and element name.
        /// </summary>
        /// <param name="message">The message to timestamp</param>
        protected void ApplyTimestamp(TMessage message)
        {
            // if this is the first time the message is submitted (as opposed to being replayed for example), 
            // make sure it has both a timestamp and a sequence number
            if (message.SequenceId == 0)
            {
                message.SequenceId = Interlocked.Increment(ref this.nextSequenceId);
            }

            if (message.Timestamp == 0)
            {
                message.Timestamp = AgentMessage.GetCurrentTime() - this.pipelineCreationTime;
            }

            if (string.IsNullOrEmpty(message.CreatingAgentName))
            {
                message.CreatingAgentName = this.creatingPipelineElementName;
            }
        }
    }
}
